import { PrismaClient, Post } from "@/generated/prisma";

export type RecommendedPost = Post & { score: number; likeCount: number; viewCount: number };

export async function recommendPosts(prisma: PrismaClient, userId: string | null, limit = 20): Promise<RecommendedPost[]> {
  const baseTrending = async () => {
    const posts = await prisma.post.findMany({
      where: { status: "PUBLISHED" },
      include: { likes: true, views: true },
      orderBy: { createdAt: "desc" },
      take: 200,
    });
    return posts.map((p: any) => ({
      ...p,
      likeCount: p.likes.length,
      viewCount: p.views.length,
      score: p.likes.length * 3 + p.views.length + Math.max(0, 50 - daysDiff(p.createdAt)),
    }));
  };

  const trending = await baseTrending();
  if (!userId) {
    return trending.sort((a, b) => b.score - a.score).slice(0, limit) as any;
  }

  const [views, likes, bookmarks] = await Promise.all([
    prisma.view.findMany({ where: { userId }, include: { post: true }, orderBy: { createdAt: "desc" }, take: 200 }),
    prisma.like.findMany({ where: { userId }, include: { post: true }, orderBy: { createdAt: "desc" }, take: 200 }),
    prisma.bookmark.findMany({ where: { userId }, include: { post: true }, orderBy: { createdAt: "desc" }, take: 200 }),
  ]);

  const interest = new Map<string, number>();
  const accumulate = (post: any, weight: number) => {
    const tags = (post.tags as any[] | null) ?? [];
    const topics = (post.topics as any[] | null) ?? [];
    for (const t of [...tags, ...topics]) {
      const key = String(t).toLowerCase();
      interest.set(key, (interest.get(key) ?? 0) + weight);
    }
  };

  views.forEach((v, idx) => accumulate((v as any).post, 1.0 * decay(idx)));
  likes.forEach((l, idx) => accumulate((l as any).post, 2.0 * decay(idx)));
  bookmarks.forEach((b, idx) => accumulate((b as any).post, 1.5 * decay(idx)));

  const interactedPostIds = new Set<string>([
    ...views.map((v) => v.postId),
    ...likes.map((l) => l.postId),
    ...bookmarks.map((b) => b.postId),
  ]);

  const recent = await prisma.post.findMany({ where: { status: "PUBLISHED" }, orderBy: { createdAt: "desc" }, take: 200 });
  const candidatesMap = new Map<string, Post>();
  for (const p of [...recent, ...trending]) {
    candidatesMap.set((p as any).id, p as unknown as Post);
  }

  const scored: RecommendedPost[] = [];
  for (const post of candidatesMap.values()) {
    if (interactedPostIds.has(post.id)) continue;
    const tags = (post as any).tags as any[] | null ?? [];
    const topics = (post as any).topics as any[] | null ?? [];
    let sim = 0;
    for (const t of [...tags, ...topics]) {
      sim += interest.get(String(t).toLowerCase()) ?? 0;
    }
    const trend = (trending as any).find((t: any) => t.id === post.id);
    const trendScore = trend ? trend.score : 0;
    const score = sim * 2 + trendScore;
    scored.push({ ...(post as any), likeCount: trend?.likeCount ?? 0, viewCount: trend?.viewCount ?? 0, score } as any);
  }

  return scored.sort((a, b) => b.score - a.score).slice(0, limit);
}

function daysDiff(date: Date) {
  const ms = Date.now() - new Date(date).getTime();
  return Math.floor(ms / 86400000);
}

function decay(rank: number) {
  return 1 / (1 + rank * 0.1);
}